Skip to content

refactor(services): wrap read/delete errors in PlayerServiceError (#56)#82

Merged
nanotaboada merged 2 commits into
masterfrom
refactor/service-errors
Apr 8, 2026
Merged

refactor(services): wrap read/delete errors in PlayerServiceError (#56)#82
nanotaboada merged 2 commits into
masterfrom
refactor/service-errors

Conversation

@nanotaboada

@nanotaboada nanotaboada commented Apr 8, 2026

Copy link
Copy Markdown
Owner

Summary

get_all, get_by_id, get_by_squad_number, and delete in src/services/player_service.rs previously returned raw diesel::result::Error, while create and update already used domain-specific CreateError and UpdateError enums. This inconsistency exposed persistence implementation details to callers.

This PR introduces PlayerServiceError { Database(diesel::result::Error) } and updates all four function signatures, aligning the full service layer API surface with the existing pattern.

Test plan

  • cargo fmt passes
  • cargo clippy --all-targets --all-features -- -D warnings passes clean
  • cargo build succeeds
  • cargo test — all 38 tests pass, no regressions

Closes #56

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • Chores
    • Improved internal error handling architecture for database operations to provide better consistency across service methods.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Apr 8, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@nanotaboada has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 17 minutes and 50 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 17 minutes and 50 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 58408aea-4f20-45f4-92d3-6279beb05b35

📥 Commits

Reviewing files that changed from the base of the PR and between 8b6c7a8 and a4a3d75.

📒 Files selected for processing (1)
  • src/services/player_service.rs

Walkthrough

The pull request introduces a PlayerServiceError domain-specific error enum and updates four read/delete methods in the player service module to return this custom error type instead of exposing diesel::result::Error directly, aligning with the existing error-handling pattern used by create and update operations.

Changes

Cohort / File(s) Summary
Error Type Definition
src/services/player_service.rs
Introduced PlayerServiceError enum wrapping diesel::result::Error under a Database variant. Updated get_all, get_by_id, get_by_squad_number, and delete methods to return Result<..., PlayerServiceError> with error mapping via .map_err(PlayerServiceError::Database) for repository errors.
Documentation
CHANGELOG.md
Documented the public API changes reflecting the return type updates for the four player service methods.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Assessment against linked issues

Objective Addressed Explanation
Define domain-specific error enum for read/delete operations [#56]
Update signatures of get_all, get_by_id, get_by_squad_number, and delete to return the new error type [#56]
Wrap diesel::result::Error values in the new Database variant [#56]
Update callers in src/routes/players.rs and affected tests to handle the new error type [#56] Changes to callers and tests are not visible in the provided code changes, making it unclear if this requirement was fully addressed.

Out-of-scope changes

No out-of-scope changes identified. All modifications directly align with the stated objective of wrapping persistence-layer errors in a domain-specific error type.

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title follows Conventional Commits format (refactor:), is descriptive and specific about wrapping read/delete errors in PlayerServiceError, and is 71 characters—under the 80-character limit.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/service-errors
  • 🛠️ sync documentation: Commit on current branch
  • 🛠️ sync documentation: Create PR
  • 🛠️ enforce http error handling: Commit on current branch
  • 🛠️ enforce http error handling: Create PR
  • 🛠️ idiomatic review: Commit on current branch
  • 🛠️ idiomatic review: Create PR
  • 🛠️ verify api contract: Commit on current branch
  • 🛠️ verify api contract: Create PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/services/player_service.rs (1)

143-153: ⚠️ Potential issue | 🟡 Minor

Fix formatting to pass CI.

The pipeline failure indicates cargo fmt detected formatting differences on the function signature. Run cargo fmt --all to apply the expected Rust formatting before merging.

As per coding guidelines: "Run cargo fmt before committing code".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/services/player_service.rs` around lines 143 - 153, The function
signature and surrounding formatting for the public delete function (pub fn
delete(conn: &mut SqliteConnection, squad_number: u32) -> Result<bool,
PlayerServiceError>) do not match project formatting; run rustfmt to fix it: run
cargo fmt --all (or rustfmt) and commit the formatted changes so the delete
function, its parameters (conn: &mut SqliteConnection, squad_number: u32), and
the return type (Result<bool, PlayerServiceError>) match CI expectations; no
code logic changes needed—just reformat and re-run CI.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/services/player_service.rs`:
- Around line 143-153: The function signature and surrounding formatting for the
public delete function (pub fn delete(conn: &mut SqliteConnection, squad_number:
u32) -> Result<bool, PlayerServiceError>) do not match project formatting; run
rustfmt to fix it: run cargo fmt --all (or rustfmt) and commit the formatted
changes so the delete function, its parameters (conn: &mut SqliteConnection,
squad_number: u32), and the return type (Result<bool, PlayerServiceError>) match
CI expectations; no code logic changes needed—just reformat and re-run CI.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: cd530404-564d-47c7-b262-54e10d6a031c

📥 Commits

Reviewing files that changed from the base of the PR and between 83cd8a1 and 8b6c7a8.

📒 Files selected for processing (2)
  • CHANGELOG.md
  • src/services/player_service.rs

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
@nanotaboada

nanotaboada commented Apr 8, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai regarding the assessment against linked issues:

Changes to callers and tests are not visible in the provided code changes, making it unclear if this requirement was fully addressed.

  • src/routes/players.rs: all read/delete handlers use wildcard error patterns (map_err(|_| Status::InternalServerError), Err(_) =>), so they compile against any error type without modification.
  • Tests: none of the read/delete tests pattern-match on the error variant — they only use is_ok(), unwrap(), is_none() — so they pass unchanged.

The callers were reviewed and found to require no updates.

@coderabbitai

coderabbitai Bot commented Apr 8, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

Let me verify the error-handling patterns in the routes and tests.


🏁 Script executed:

#!/bin/bash
echo "=== src/routes/players.rs: map_err and Err patterns ==="
rg -n 'map_err|Err\(' src/routes/players.rs

echo ""
echo "=== tests: error pattern-matching on read/delete results ==="
rg -n 'is_ok\(\)|is_none\(\)|unwrap\(\)|is_err\(\)|PlayerServiceError|Database(' tests/

Length of output: 1390


🏁 Script executed:

#!/bin/bash
echo "=== tests: error and result assertion patterns ==="
rg -n 'is_ok\(\)|is_none\(\)|unwrap\(\)|is_err\(\)|PlayerServiceError|Database\(' tests/

Length of output: 6020


@nanotaboada, the explanation is confirmed by the code. Both routes and tests are verified:

  • Routes (src/routes/players.rs): get_all (line 34), get_by_id (line 56), and get_by_squad_number (line 80) all use .map_err(|_| Status::InternalServerError), and delete (line 169) uses Err(_) => Err(Status::InternalServerError) — all wildcard patterns that are type-agnostic and compile cleanly against PlayerServiceError without modification.
  • Tests (tests/player_service_tests.rs, tests/player_routes_tests.rs, tests/concurrency_tests.rs): No test pattern-matches on a specific error variant for read/delete operations. All assertions use is_ok(), is_err(), .unwrap(), or is_none(), so they pass unchanged. (Contrast with create/update handlers at lines 112–113 and 146–147, which do match on specific variants — but those are unaffected by this PR.)

The callers were correctly found to require no updates. I'll update the long-term memory to reflect the new error-wrapping approach introduced in this PR.


✏️ Learnings added
Learnt from: nanotaboada
URL: https://github.com/nanotaboada/rust-samples-rocket-restful/pull/82

Timestamp: 2026-04-08T14:40:53.512Z
Learning: In `src/services/player_service.rs` (as of PR `#82`), the functions `get_all`, `get_by_id`, `get_by_squad_number`, and `delete` now return `Result<T, PlayerServiceError>` (wrapping `diesel::result::Error` via `PlayerServiceError::Database`), not `diesel::result::Error` directly. Callers in `src/routes/players.rs` use wildcard error patterns (`map_err(|_| Status::InternalServerError)`, `Err(_) =>`), so they required no modification. Tests assert only with `is_ok()`, `is_err()`, `unwrap()`, and `is_none()` — no tests pattern-match on `PlayerServiceError` variants.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


⛔ Learnings removed
Learnt from: nanotaboada
Repo: nanotaboada/rust-samples-rocket-restful PR: 74
File: src/services/player_service.rs:3-10
Timestamp: 2026-04-08T01:46:25.312Z
Learning: In `src/services/player_service.rs`, the service layer intentionally uses `&mut diesel::SqliteConnection` in all public function signatures and returns `diesel::result::Error` directly for read operations (`get_all`, `get_by_id`, `get_by_squad_number`, `delete`). This is by design: services are the connection-passing boundary (routes acquire `&mut conn` from the r2d2 pool and pass it down), while repositories own all Diesel DSL and `crate::schema` references. Wrapping `diesel::result::Error` in a separate `ServiceError` enum for these functions was considered unnecessary indirection for this project.

🧠 Learnings used
Learnt from: nanotaboada
Repo: nanotaboada/rust-samples-rocket-restful PR: 49
File: tests/player_routes_tests.rs:70-79
Timestamp: 2026-03-29T17:09:18.097Z
Learning: In `tests/player_routes_tests.rs` (and the corresponding handler in src/routes/health.rs), the `health()` Rocket route handler returns only an HTTP `Status` with no response body. Calling `response.into_string()` on a bodyless Rocket response yields `None`, so `.unwrap()` would panic. Tests for this endpoint should assert only the status code (`Status::Ok`), not a response body.

Learnt from: CR
Repo: nanotaboada/rust-samples-rocket-restful PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-04-08T01:55:37.411Z
Learning: Applies to **/*.rs : Avoid `unwrap()`, `expect()`, unnecessary `.clone()`, blocking operations in async handlers, missing `?` error propagation, and inline comments between AAA test sections

Learnt from: CR
Repo: nanotaboada/rust-samples-rocket-restful PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-04-08T01:55:37.411Z
Learning: Applies to tests/**/*.rs : Use `player_request_for_creation()`, `player_request_for_update()`, and `EXISTING_PLAYER_ID` from `tests/common` for request bodies and UUID-based GET tests; never hardcode UUID strings inline

Learnt from: CR
Repo: nanotaboada/rust-samples-rocket-restful PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-04-08T01:55:37.411Z
Learning: Applies to src/routes/**/*.rs : Use UUID (v4) with serde features for surrogate key lookups via `GET /players/{uuid}`; use `squad_number` as natural key for all mutation routes: `PUT` and `DELETE /players/squadnumber/{squad_number}`

Learnt from: nanotaboada
Repo: nanotaboada/rust-samples-rocket-restful PR: 74
File: src/services/player_service.rs:3-10
Timestamp: 2026-04-08T01:46:25.312Z
Learning: In `src/services/player_service.rs`, the service layer intentionally uses `&mut diesel::SqliteConnection` in all public function signatures and returns `diesel::result::Error` directly for read operations (`get_all`, `get_by_id`, `get_by_squad_number`, `delete`). This is by design: services are the connection-passing boundary (routes acquire `&mut conn` from the r2d2 pool and pass it down), while repositories own all Diesel DSL and `crate::schema` references. Wrapping `diesel::result::Error` in a separate `ServiceError` enum for these functions was considered unnecessary indirection for this project.

Learnt from: CR
Repo: nanotaboada/rust-samples-rocket-restful PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-04-08T01:55:37.411Z
Learning: Applies to src/services/**/*.rs : Services must not reference Diesel or SQL directly; they must contain pure business logic and return `Result` types

Learnt from: nanotaboada
Repo: nanotaboada/rust-samples-rocket-restful PR: 74
File: src/services/player_service.rs:50-51
Timestamp: 2026-04-08T01:46:19.698Z
Learning: In `src/services/player_service.rs` (and related repository/route files), `squad_number` is a football squad number constrained to the range 1–99 (seed data uses 1–27). The `u32 as i32` cast is intentional and safe for this domain; flagging it as an overflow risk is a false positive. No validation guard for values above `i32::MAX` is needed.

Learnt from: nanotaboada
Repo: nanotaboada/rust-samples-rocket-restful PR: 74
File: src/services/player_service.rs:3-10
Timestamp: 2026-04-08T01:46:25.312Z
Learning: In `src/services/player_service.rs`, the module-level doc comment intentionally reads "connection-aware but DSL-free" to reflect that the service layer accepts `&mut SqliteConnection` but contains no Diesel query DSL (no `diesel::prelude`, no `crate::schema` imports). All persistence DSL lives in `src/repositories/player_repository.rs`.

Learnt from: nanotaboada
Repo: nanotaboada/rust-samples-rocket-restful PR: 55
File: src/state/player_collection.rs:34-40
Timestamp: 2026-04-05T00:23:45.165Z
Learning: In `src/state/player_collection.rs`, the `initialize_database()` function uses an emptiness check (`is_empty`) as a bootstrap signal rather than schema-based first-run detection. This is intentional for this PR: a committed pre-seeded DB is shipped, so the emptiness check only acts as a safety net for fresh clones. The deeper fix (schema-based first-run detection, transactional seed, `Result` propagation instead of `expect()`/`unwrap()`) is deferred to the upcoming migrations PR.

@codecov

codecov Bot commented Apr 8, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 84.33%. Comparing base (83cd8a1) to head (a4a3d75).
⚠️ Report is 3 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master      #82      +/-   ##
==========================================
+ Coverage   84.14%   84.33%   +0.19%     
==========================================
  Files           6        6              
  Lines         164      166       +2     
==========================================
+ Hits          138      140       +2     
  Misses         26       26              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@nanotaboada
nanotaboada merged commit e1602d6 into master Apr 8, 2026
10 checks passed
@nanotaboada
nanotaboada deleted the refactor/service-errors branch April 8, 2026 14:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Refactor player service to wrap read/delete errors in domain-specific error types

1 participant